package net.oschina.renyong.query.generator;

import java.io.File;
import java.sql.Connection;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.apache.velocity.VelocityContext;
import org.springframework.util.StringUtils;

import com.baomidou.mybatisplus.core.parser.SqlInfo;
import com.baomidou.mybatisplus.extension.plugins.pagination.optimize.JsqlParserCountOptimize;
import com.baomidou.mybatisplus.generator.config.ConstVal;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;

import net.oschina.renyong.query.generator.constant.FunctionReturnTypeEnum;
import net.oschina.renyong.query.generator.plugin.ICountSqlOptimizer;
import net.oschina.renyong.query.generator.po.ConfigQuery;
import net.oschina.renyong.query.generator.po.ConfigViews;
import net.oschina.renyong.query.generator.po.ContentField;
import net.oschina.renyong.query.generator.po.ContentQuery;
import net.oschina.renyong.query.generator.util.QuerySqlUtil;
import net.sf.jsqlparser.JSQLParserException;

/**
 * 特殊查询代码生成器<br>
 * @author Timothy
 */
public class QuerySqlGenerator {

	/**
	 * 全局配置，这里面真正使用到的配置有：outputDir, open, author, dateType;
	 * outputDir：项目路径
	 */
	private GlobalConfig globalConfig;
	/** 数据库配置 */
	private DataSourceConfig dataSourceConfig;
	/** 包 相关配置 */
	private PackageConfig packageConfig;
	/** 命名策略 */
	private NamingStrategy namingStrategy;

	/** 查询总数优化器 */
	private ICountSqlOptimizer countSqlOptimizer;
	/** query_sql.xml所在的资源路径 */
	private String resourceLocation;

	/**
	 * 执行特殊查询的代码生成
	 */
	public void executeGenerate() {
		// 获取main/java的绝对路径
		String mainJavaDir = getMainJavaDir(globalConfig.getOutputDir());
		// 获取main/resources的绝对路径
		String mainResourcesDir = getMainResourcesDir(globalConfig.getOutputDir());

		// 获取数据库连接
		Connection connection = dataSourceConfig.getConn();
		if (connection == null) {
			System.err.println("数据库连接失败，代码生成中止！");
			return;
		}

		// 获取各生成文件所在的包路径
		Map<String, String> packageInfo = getPackageInfo(packageConfig);
		// 获取各包路径对应的文件路径
		Map<String, String> pathInfo = getPathInfo(packageInfo, mainJavaDir, mainResourcesDir);

		try {
			// 解析获取特殊查询配置集ConfigViews
			ConfigViews configViews = QuerySqlXmlService.getConfigViews(resourceLocation);
			// 获取 类型-函数名集合 映射
			Map<FunctionReturnTypeEnum, List<String>> functionReturnTypeMap = getFunctionReturnTypeMap(configViews);
			List<ConfigQuery> configQueries = configViews.getConfigQueries();
			for (ConfigQuery configQuery : configQueries) {
				oneViewGenerate(configQuery, packageInfo, pathInfo, functionReturnTypeMap, globalConfig,
						dataSourceConfig, connection);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		try {
			connection.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 对生成的查询类文件进行删除
	 * @param configQueryNameArr 待删除的查询配置名数组
	 */
	public void executeDelete(String[] configQueryNameArr) {
		// 获取main/java的绝对路径
		String mainJavaDir = getMainJavaDir(globalConfig.getOutputDir());
		// 获取main/resources的绝对路径
		String mainResourcesDir = getMainResourcesDir(globalConfig.getOutputDir());
		// 获取各生成文件所在的包路径
		Map<String, String> packageInfo = getPackageInfo(packageConfig);
		Map<String, String> pathInfo = getPathInfo(packageInfo, mainJavaDir, mainResourcesDir);
		for (String configName : configQueryNameArr) {
			oneViewDelete(configName, pathInfo);
		}
	}

	private void oneViewDelete(String queryConfigName, Map<String, String> pathInfo) {
		ContentQuery contentQuery = new ContentQuery();
		// 获取并设置实体类名
		contentQuery.setEntityName(
				NamingStrategy.capitalFirst(QuerySqlUtil.processUnderlineName(queryConfigName, getNamingStrategy())));
		// 获取实体类文件全路径名：包输出路径+"/"+实体类名+".java"
		String entityFile = pathInfo.get(ConstVal.ENTITY_PATH) + File.separator + contentQuery.getEntityName()
				+ ConstVal.JAVA_SUFFIX;
		// 获取Mapper类文件全路径名：包输出路径+"/"+Mapper类名+".java"
		String mapperFile = pathInfo.get(ConstVal.MAPPER_PATH) + File.separator + contentQuery.getMapperName()
				+ ConstVal.JAVA_SUFFIX;
		// 获取xml文件全路径名
		String xmlFile = pathInfo.get(ConstVal.XML_PATH) + File.separator + contentQuery.getXmlName()
				+ ConstVal.XML_SUFFIX;
		// 获取Service类文件全路径名：包输出路径+"/"+Service类名+".java"
		String serviceFile = pathInfo.get(ConstVal.SERVICE_PATH) + File.separator + contentQuery.getServiceName()
				+ ConstVal.JAVA_SUFFIX;
		// 获取ServiceImpl类文件全路径名：包输出路径+"/"+ServiceImpl类名+".java"
		String implFile = pathInfo.get(ConstVal.SERVICE_IMPL_PATH) + File.separator + contentQuery.getServiceImplName()
				+ ConstVal.JAVA_SUFFIX;

		deleteFile(entityFile);
		deleteFile(mapperFile);
		deleteFile(xmlFile);
		deleteFile(serviceFile);
		deleteFile(implFile);
	}

	private void deleteFile(String filePath) {
		File file = new File(filePath);
		if (file.exists()) {
			boolean result = file.delete();
			if (result) {
				System.out.println(filePath + " 删除成功");
			} else {
				System.out.println(filePath + " 删除失败");
			}
		} else {
			System.out.println(filePath + " 不存在");
		}
	}

	/**
	 * 一条查询的代码生成
	 * @param configQuery 一个查询配置
	 * @param packageInfo 各个待生成类所在的包路径
	 * @param pathInfo 各个待生成文件的目录地址
	 * @param functionReturnTypeMap 函数返回的数据类型MAP
	 * @param globalConfig 全局配置
	 * @param dataSourceConfig 数据源配置
	 * @param connection 数据源连接
	 */
	private void oneViewGenerate(ConfigQuery configQuery, Map<String, String> packageInfo, Map<String, String> pathInfo,
			Map<FunctionReturnTypeEnum, List<String>> functionReturnTypeMap, GlobalConfig globalConfig,
			DataSourceConfig dataSourceConfig, Connection connection) {
		String configName = configQuery.getName();
		String sqlForParse = configQuery.getSqlForParse();

		try {
			// 解析查询语句获取select字段集合
			List<ContentField> selectFieldList = QuerySqlParser.parseGetContentFieldList(sqlForParse, configName,
					getNamingStrategy(), functionReturnTypeMap, true);
			// 解析获取非select字段集合
			List<ContentField> noSelectFieldList = parseGetNoSelectFieldList(configQuery, functionReturnTypeMap);
			// 整合成 表名字段名-查询字段集合 MAP
			Map<String, ContentField> contentFieldByTblColumnMap = getContentFieldByTblColumnMap(selectFieldList,
					noSelectFieldList);
			// 解析得到这些查询字段对应的所有表名
			Set<String> allTbls = getAllTbls(selectFieldList, noSelectFieldList);
			// 补充表字段的数据类型，这也是对字段有效性的识别过程
			for (String tblName : allTbls) {
				// 查询数据库，填充各个字段的信息
				TblParser.parseTblAndFillColumn(tblName, contentFieldByTblColumnMap, globalConfig, dataSourceConfig,
						connection);
			}

			// 字段内容的特殊替换，目的在于绕过sql解析错误问题，目前只替换字段全名
			doFieldReplace(selectFieldList, noSelectFieldList, configQuery.getReplaceField());

			// 获取对应的查询内容信息
			ContentQuery contentQuery = getContent(configQuery, selectFieldList, noSelectFieldList, getNamingStrategy(),
					countSqlOptimizer);
			VelocityContext context = QueryFileGenerator.generateVelocity(contentQuery, packageInfo, globalConfig);
			// 文件生成
			QueryFileGenerator.batchOutput(contentQuery, context, pathInfo);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 解析获取非select字段内容集合
	 * @param configQuery 一个查询配置
	 * @param functionReturnTypeMap 函数返回类型 MAP
	 * @return 非select字段内容集合
	 * @throws JSQLParserException
	 */
	private List<ContentField> parseGetNoSelectFieldList(ConfigQuery configQuery,
			Map<FunctionReturnTypeEnum, List<String>> functionReturnTypeMap) throws JSQLParserException {
		if (!StringUtils.isEmpty(configQuery.getNoSelectField())) {
			// 存在非select字段
			return QuerySqlParser.parseGetNoSelectFieldList(configQuery.getSqlForParse(), configQuery.getName(),
					getNamingStrategy(), functionReturnTypeMap, configQuery.getNoSelectField());
		}
		return null;
	}

	/**
	 * 获取src/main/java的全路径名
	 * @return src/main/java的全路径名
	 */
	private static String getMainJavaDir(String projectDir) {
		return projectDir + "/src/main/java";
	}

	/**
	 * 获取src/main/resources的全路径名
	 * @return src/main/resources的全路径名
	 */
	private static String getMainResourcesDir(String projectDir) {
		return projectDir + "/src/main/resources";
	}

	/**
	 * 解析获取 表名列名-字段内容 MAP
	 * @param selectFieldList select字段集合
	 * @param noSelectFieldList 非select字段集合
	 * @return 表名列名-字段内容 MAP
	 */
	private static Map<String, ContentField> getContentFieldByTblColumnMap(List<ContentField> selectFieldList,
			List<ContentField> noSelectFieldList) {
		Map<String, ContentField> map = selectFieldList.stream()
				// 过滤保留那些表名和列名都存在的字段内容
				.filter(v -> v.getTableName() != null && v.getColumnName() != null)
				.collect(Collectors.toMap(ContentField::getTblColumn, Function.identity()));
		if (noSelectFieldList != null) {
			Map<String, ContentField> noSelectMap = noSelectFieldList.stream()
					// 过滤保留那些表名和列名都存在的字段内容
					.filter(v -> v.getTableName() != null && v.getColumnName() != null)
					.collect(Collectors.toMap(ContentField::getTblColumn, Function.identity()));
			map.putAll(noSelectMap);
		}
		return map;
	}

	/**
	 * 解析得到这些查询字段对应的所有表名
	 * @param selectFieldList 查询字段集合
	 * @return 查询字段集合对应的所有表名
	 */
	private static Set<String> getAllTbls(List<ContentField> selectFieldList, List<ContentField> noSelectFieldList) {
		Set<String> allTbls = selectFieldList.stream()
				// 过滤保留表名存在的数据
				.filter(v -> v.getTableName() != null)
				// 映射得到表名
				.map(ContentField::getTableName)
				// 归集
				.collect(Collectors.toSet());
		if (noSelectFieldList != null) {
			Set<String> tbls = noSelectFieldList.stream()
					// 过滤保留表名存在的数据
					.filter(v -> v.getTableName() != null)
					// 映射得到表名
					.map(ContentField::getTableName)
					// 归集
					.collect(Collectors.toSet());
			allTbls.addAll(tbls);
		}
		return allTbls;
	}

	/**
	 * 获取各生成文件所在的包路径
	 * @return 文件类型-包路径 映射
	 */
	private static Map<String, String> getPackageInfo(PackageConfig config) {
		Map<String, String> packageInfo = new HashMap<>();
		packageInfo.put(ConstVal.ENTITY, QuerySqlUtil.joinPackage(config.getParent(), config.getEntity()));
		packageInfo.put(ConstVal.MAPPER, QuerySqlUtil.joinPackage(config.getParent(), config.getMapper()));
		packageInfo.put(ConstVal.XML, QuerySqlUtil.joinPackage(config.getParent(), config.getXml()));
		packageInfo.put(ConstVal.SERVICE, QuerySqlUtil.joinPackage(config.getParent(), config.getService()));
		packageInfo.put(ConstVal.SERVICE_IMPL, QuerySqlUtil.joinPackage(config.getParent(), config.getServiceImpl()));
		// packageInfo.put(ConstVal.CONTROLLER, QuerySqlUtil.joinPackage(config.getParent(), config.getController()));
		return packageInfo;
	}

	/**
	 * 获取各包路径对应的文件路径
	 * @param packageInfo 各包路径
	 * @param mainJavaDir src/main/java 所在的全路径
	 * @param mainResourcesDir src/main/resources 所在的全路径
	 * @return 各包路径对应的文件路径
	 */
	private static Map<String, String> getPathInfo(Map<String, String> packageInfo, String mainJavaDir,
			String mainResourcesDir) {
		Map<String, String> pathInfo = new HashMap<>();
		pathInfo.put(ConstVal.ENTITY_PATH, QuerySqlUtil.joinPath(mainJavaDir, packageInfo.get(ConstVal.ENTITY)));
		pathInfo.put(ConstVal.MAPPER_PATH, QuerySqlUtil.joinPath(mainJavaDir, packageInfo.get(ConstVal.MAPPER)));
		pathInfo.put(ConstVal.XML_PATH, QuerySqlUtil.joinPath(mainResourcesDir, packageInfo.get(ConstVal.XML)));
		pathInfo.put(ConstVal.SERVICE_PATH, QuerySqlUtil.joinPath(mainJavaDir, packageInfo.get(ConstVal.SERVICE)));
		pathInfo.put(ConstVal.SERVICE_IMPL_PATH,
				QuerySqlUtil.joinPath(mainJavaDir, packageInfo.get(ConstVal.SERVICE_IMPL)));
		for (String dir : pathInfo.values()) {
			new File(dir).mkdirs();
		}
		return pathInfo;
	}

	/**
	 * 获取 返回类型-函数名集合 映射
	 * @param configViews ConfigViews
	 * @return 返回类型-函数名集合 映射
	 */
	private static Map<FunctionReturnTypeEnum, List<String>> getFunctionReturnTypeMap(ConfigViews configViews) {
		Map<FunctionReturnTypeEnum, List<String>> functionReturnTypeMap = new HashMap<>();
		// 目前有整数函数，字符串函数
		String integerFunctionStr = configViews.getIntegerFunction();
		String stringFunctionStr = configViews.getStringFunction();

		if (!StringUtils.isEmpty(integerFunctionStr)) {
			String[] integerFunctionArr = integerFunctionStr.trim().split(",");
			List<String> integerFunctionList = Arrays.asList(integerFunctionArr);
			functionReturnTypeMap.put(FunctionReturnTypeEnum.INTEGER, integerFunctionList);
		}
		if (!StringUtils.isEmpty(stringFunctionStr)) {
			String[] stringFunctionArr = stringFunctionStr.trim().split(",");
			List<String> stringFunctionList = Arrays.asList(stringFunctionArr);
			functionReturnTypeMap.put(FunctionReturnTypeEnum.STRING, stringFunctionList);
		}
		return functionReturnTypeMap;
	}

	/**
	 * 字段内容的特殊替换，目的在于绕过sql解析错误问题，目前只替换字段全名
	 * @param selectFieldList select字段内容集合
	 * @param noSelectFieldList 非select字段内容集合
	 * @param replaceField 替换内容
	 */
	private static void doFieldReplace(List<ContentField> selectFieldList, List<ContentField> noSelectFieldList,
			String replaceField) {
		Map<String, String> aliasFullNameMap = new HashMap<>();

		// 替换内容 生成 别名-字段全名 MAP
		if (!StringUtils.isEmpty(replaceField)) {
			// 换行替换成空格
			replaceField = replaceField.replace("\r", " ").replace("\n", " ");
			// 英文逗号分隔出不同的字段
			String[] replaceArr = replaceField.split(",");
			for (String replace : replaceArr) {
				// 以" as "做分隔字段，忽略大小写；分隔出字段全名和字段别名
				String[] fieldArr = replace.split("(?i) as ");
				if (fieldArr.length >= 2) {
					String aliasName = fieldArr[1].trim();
					String columnFullName = fieldArr[0].trim();
					aliasFullNameMap.put(aliasName, columnFullName);
				}
			}
		}

		if (aliasFullNameMap.size() == 0) {
			// 没有可替换的字段，直接返回
			return;
		}

		// select字段集合的字段全名替换
		Map<String, ContentField> selectFieldMap = selectFieldList.stream()
				.collect(Collectors.toMap(ContentField::getAliasName, Function.identity()));
		aliasFullNameMap.keySet().stream().filter(v -> selectFieldMap.containsKey(v)).forEach(v -> {
			String columnFullName = aliasFullNameMap.get(v);
			ContentField field = selectFieldMap.get(v);
			field.setColumnFullName(columnFullName);
		});

		// 非select字段集合的字段全名替换
		if (noSelectFieldList != null && noSelectFieldList.size() > 0) {
			Map<String, ContentField> noSelectFieldMap = noSelectFieldList.stream()
					.collect(Collectors.toMap(ContentField::getAliasName, Function.identity()));
			aliasFullNameMap.keySet().stream().filter(v -> noSelectFieldMap.containsKey(v)).forEach(v -> {
				String columnFullName = aliasFullNameMap.get(v);
				ContentField field = noSelectFieldMap.get(v);
				field.setColumnFullName(columnFullName);
			});
		}
	}

	/**
	 * 生成这条查询的查询内容信息
	 * @param queryConfig 查询的配置
	 * @param selectFieldList 查询中select字段信息集合
	 * @param noSelectFieldList 查询中非select字段信息集合
	 * @param namingStrategy 命名策略
	 * @param countSqlOptimizer 查询总数SQL优化器
	 * @return 查询对应的查询内容信息
	 */
	private static ContentQuery getContent(ConfigQuery queryConfig, List<ContentField> selectFieldList,
			List<ContentField> noSelectFieldList, NamingStrategy namingStrategy, ICountSqlOptimizer countSqlOptimizer) {
		ContentQuery contentQuery = new ContentQuery(countSqlOptimizer);
		contentQuery.setEntityName(
				NamingStrategy.capitalFirst(QuerySqlUtil.processUnderlineName(queryConfig.getName(), namingStrategy)));
		contentQuery.setSelectFieldList(selectFieldList);
		contentQuery.setNoSelectFieldList(noSelectFieldList);
		contentQuery.setComment(queryConfig.getComment());
		contentQuery.setViewSql(queryConfig.getSql());
		if (queryConfig.getSqlCount() != null && queryConfig.getSqlCount()) {
			SqlInfo sqlInfo = new JsqlParserCountOptimize().parser(null, queryConfig.getSql());
			contentQuery.setCountSql(sqlInfo.getSql());
		}
		return contentQuery;
	}

	public GlobalConfig getGlobalConfig() {
		return globalConfig;
	}

	public void setGlobalConfig(GlobalConfig globalConfig) {
		this.globalConfig = globalConfig;
	}

	public DataSourceConfig getDataSourceConfig() {
		return dataSourceConfig;
	}

	public void setDataSourceConfig(DataSourceConfig dataSourceConfig) {
		this.dataSourceConfig = dataSourceConfig;
	}

	public PackageConfig getPackageConfig() {
		return packageConfig;
	}

	public void setPackageConfig(PackageConfig packageConfig) {
		this.packageConfig = packageConfig;
	}

	public NamingStrategy getNamingStrategy() {
		return namingStrategy;
	}

	public void setNamingStrategy(NamingStrategy namingStrategy) {
		this.namingStrategy = namingStrategy;
	}

	public ICountSqlOptimizer getCountSqlOptimizer() {
		return countSqlOptimizer;
	}

	public void setCountSqlOptimizer(ICountSqlOptimizer countSqlOptimizer) {
		this.countSqlOptimizer = countSqlOptimizer;
	}

	public String getResourceLocation() {
		return resourceLocation;
	}

	public void setResourceLocation(String resourceLocation) {
		this.resourceLocation = resourceLocation;
	}
}
